package cn.assumejson.springframework.aop.aspectj.annotation;

import cn.assumejson.springframework.aop.Advisor;
import cn.assumejson.springframework.aop.ClassFilter;
import cn.assumejson.springframework.aop.MethodMatcher;
import cn.assumejson.springframework.aop.Pointcut;
import cn.assumejson.springframework.aop.aspectj.AspectJExpressionPointcut;
import cn.assumejson.springframework.aop.support.DefaultPointcutAdvisor;
import cn.assumejson.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import cn.assumejson.springframework.beans.factory.support.DefaultListableBeanFactory;
import cn.assumejson.springframework.core.util.ReflectionUtils;
import cn.hutool.core.lang.Assert;
import org.aopalliance.aop.Advice;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.AjType;
import org.aspectj.lang.reflect.AjTypeSystem;
import org.aspectj.lang.reflect.PerClauseKind;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * @author gang.Li
 * @version 1.0
 * @since 1.0 2021/12/9 15:12
 */
public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFactory implements Serializable {

    private ConfigurableListableBeanFactory beanFactory;

    public ReflectiveAspectJAdvisorFactory(ConfigurableListableBeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    @Override
    public List<Advisor> getAdvisors(BeanFactoryAspectInstanceFactory factory) {
        // 获取切面class 对象
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) factory.getBeanFactory();
        String beanName = factory.getName();

        Class<?> aspectjClass = beanFactory.getBeanDefinition(beanName).getBeanClass();
        String aspectName = beanName;
        // 解析 aspectJ
        validate(aspectjClass);

        // 获取所有的Advisors
        List<Advisor> advisors = new ArrayList<>();
        for (Method method : getAdvisorMethods(aspectjClass)) {
            // 根据方法生成Advisor
            Advisor advisor = getAdvisor(method, aspectName, aspectjClass);
            advisors.add(advisor);
        }

        if (!advisors.isEmpty()) {
            // 生成切面Bean
            AspectJExpressionPointcut declaredPointcut = ((InstantiationModelAwarePointcutAdvisorImpl) advisors.get(0)).getDeclaredPointcut();
            Advice advice = ((InstantiationModelAwarePointcutAdvisorImpl) advisors.get(0)).getAdvice();

            Advisor instantiationAdvisor = new DefaultPointcutAdvisor(declaredPointcut, advice);
            advisors.add(instantiationAdvisor);
        }

        return advisors;
    }

    private Advisor getAdvisor(Method method, String aspectName, Class<?> aspectjClass) {
        // 获取方法上的pointCut 注解
        AspectJExpressionPointcut expressionPointcut = getPointcut(method, aspectjClass);
        if (expressionPointcut == null) {
            return null;
        }

        return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, method,
                this, aspectName);
    }

    private AspectJExpressionPointcut getPointcut(Method method, Class<?> aspectjClass) {
        // 拿到切面中某个方法上的注解信息
        AspectJAnnotation<?> aspectJAnnotation =
                AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(method);

        if (aspectJAnnotation == null) {
            return null;
        }
        AspectJExpressionPointcut ajexp =
                new AspectJExpressionPointcut(aspectjClass, new String[0], new Class<?>[0]);
        ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
        if (this.beanFactory != null) {
            ajexp.setBeanFactory(this.beanFactory);
        }
        return ajexp;
    }

    private List<Method> getAdvisorMethods(Class<?> aspectjClass) {
        // 拿到所有的切面，除了使用@Pointcut 注解的方法
        List<Method> methods = new ArrayList<>();
        // 拿到切面类中所有没有加@Pointcut的方法
        ReflectionUtils.doWithMethods(aspectjClass, methods::add);

        return methods;
    }

    private void validate(Class<?> aspectjClass) {
        Assert.notNull(aspectjClass.getAnnotation(Aspect.class), "[" + aspectjClass.getName() + "] cannot extend concrete aspect [" +
                aspectjClass.getSuperclass().getName() + "]");

        // 使用org.aspectj.lang.reflect.AjType; 解析 切面
        AjType<?> ajType = AjTypeSystem.getAjType(aspectjClass);

        Assert.isTrue(ajType.isAspect());

        Assert.isFalse(ajType.getPerClause().getKind() == PerClauseKind.PERCFLOW,
                aspectjClass.getName() + " uses percflow instantiation model: " +
                        "This is not supported in Spring AOP.");

        Assert.isFalse(ajType.getPerClause().getKind() == PerClauseKind.PERCFLOWBELOW,
                aspectjClass.getName() + " uses percflowbelow instantiation model: " +
                        "This is not supported in Spring AOP.");

    }
}
